/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the EternalTech source code.

 The EternalTech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The EternalTech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the EternalTech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// sg_gameState.cpp - ...
//


#include "sg_local.h"


/*
 ==============================================================================

 CONFIG STRINGS

 ==============================================================================
*/


/*
 ==================
 SG_SetConfigString
 ==================
*/
void SG_SetConfigString (int index, const char *string){

	if (index < 0 || index >= MAX_CONFIG_STRINGS)
		G_Error("SG_SetConfigString: index out of range");

	if (!string)
		string = "";

	if (level.configStrings[index]){
		// Don't bother updating if no change
		if (!Str_Compare(level.configStrings[index], string))
			return;

		// Free the string
		glqMemory->Free(level.configStrings[index]);
	}

	// Copy the string
	level.configStrings[index] = glqMemory->DupString(string, TAG_SERVER);

	// Send it to all the clients if we are active or restarting
	if (level.state != GS_ACTIVE && !level.restarting)
		return;

	glqServer->BroadcastReliableCommand("cs %i %s", index, string);
}

/*
 ==================
 SG_ConfigStringIndex
 ==================
*/
int SG_ConfigStringIndex (const char *string, int start, int max){

	int		index;

	if (!string || !string[0])
		return 0;

	for (index = 1; index < max; index++){
		if (!level.configStrings[start + index])
			break;

		if (!Str_ICompare(level.configStrings[start + index], string))
			return index;
	}

	if (index == max)
		G_Error("SG_ConfigStringIndex: overflow");

	// Update the config string
	SG_SetConfigString(start + index, string);

	return index;
}

/*
 ==================
 SG_ModelIndex
 ==================
*/
int SG_ModelIndex (const char *name){

	return SG_ConfigStringIndex(name, CS_MODELS, MAX_GAME_MODELS);
}

/*
 ==================
 SG_MaterialIndex
 ==================
*/
int SG_MaterialIndex (const char *name){

	return SG_ConfigStringIndex(name, CS_MATERIALS, MAX_GAME_MATERIALS);
}

/*
 ==================
 SG_SoundShaderIndex
 ==================
*/
int SG_SoundShaderIndex (const char *name){

	return SG_ConfigStringIndex(name, CS_SOUND_SHADERS, MAX_GAME_SOUND_SHADERS);
}


/*
 ==============================================================================

 BASELINES (WRITING)

 ==============================================================================
*/


/*
 ==================
 SG_WriteBaseline_General
 ==================
*/
static void SG_WriteBaseline_General (msg_t *msg, entityState_t *baseline){

	general_t	*general = (general_t *)baseline->buffer;

	glqMsgSystem->WriteDeltaLong(msg, 0, general->contents);
	glqMsgSystem->WriteDeltaLong(msg, 0, general->bounds);
	glqMsgSystem->WriteDeltaBits(msg, 0, general->model, ES_MODEL_BITS);
	glqMsgSystem->WriteDeltaBits(msg, 0, general->clipModel, ES_CLIP_MODEL_BITS);
	glqMsgSystem->WriteDeltaBits(msg, 0, general->material, ES_MATERIAL_BITS);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, general->materialParms[0]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, general->materialParms[1]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, general->materialParms[2]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, general->materialParms[3]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, general->materialParms[4]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, general->materialParms[5]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, general->materialParms[6]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, general->materialParms[7]);
	glqMsgSystem->WriteDeltaBits(msg, 0, general->sound, ES_SOUND_SHADER_BITS);
	glqMsgSystem->WriteDeltaLong(msg, 0, general->soundTime);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, general->soundParms[0]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, general->soundParms[1]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, general->soundParms[2]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, general->soundParms[3]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, general->soundParms[4]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, general->soundParms[5]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, general->soundParms[6]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, general->soundParms[7]);
}

/*
 ==================

 ==================
*/
static void SG_WriteBaseline_Mover (msg_t *msg, entityState_t *baseline){

	mover_t	*mover = (mover_t *)baseline->buffer;

	glqMsgSystem->WriteDeltaBits(msg, 0, mover->model, ES_MODEL_BITS);
}

/*
 ==================

 ==================
*/
static void SG_WriteBaseline_Beam (msg_t *msg, entityState_t *baseline){

}

/*
 ==================
 SG_WriteBaseline_Light
 ==================
*/
static void SG_WriteBaseline_Light (msg_t *msg, entityState_t *baseline){

	light_t	*light = (light_t *)baseline->buffer;

	glqMsgSystem->WriteDeltaBits(msg, 0, light->type, 2);
	glqMsgSystem->WriteDeltaVec3(msg, vec3_origin, light->center);
	glqMsgSystem->WriteDeltaVec3(msg, vec3_origin, light->radius);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, light->xMin);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, light->xMax);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, light->yMin);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, light->yMax);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, light->zNear);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, light->zFar);
	glqMsgSystem->WriteBool(msg, light->noShadows);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, light->shadowBias);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, light->shadowSlopeScaleBias);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, light->shadowSoftness);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, light->fogDistance);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, light->fogHeight);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, light->volumeIntensity);
	glqMsgSystem->WriteBool(msg, light->volumeShadows);
	glqMsgSystem->WriteBool(msg, light->volumeInteractions);
	glqMsgSystem->WriteDeltaBits(msg, 0, light->detailLevel, 2);
	glqMsgSystem->WriteDeltaBits(msg, 0, light->material, ES_MATERIAL_BITS);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, light->materialParms[0]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, light->materialParms[1]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, light->materialParms[2]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, light->materialParms[3]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, light->materialParms[4]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, light->materialParms[5]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, light->materialParms[6]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, light->materialParms[7]);
}

/*
 ==================
 SG_WriteBaseline_Speaker
 ==================
*/
static void SG_WriteBaseline_Speaker (msg_t *msg, entityState_t *baseline){

	speaker_t	*speaker = (speaker_t *)baseline->buffer;

	glqMsgSystem->WriteDeltaNormal(msg, vec3_origin, speaker->direction);
	glqMsgSystem->WriteDeltaBits(msg, 0, speaker->sound, ES_SOUND_SHADER_BITS);
	glqMsgSystem->WriteDeltaLong(msg, 0, speaker->soundTime);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, speaker->soundParms[0]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, speaker->soundParms[1]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, speaker->soundParms[2]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, speaker->soundParms[3]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, speaker->soundParms[4]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, speaker->soundParms[5]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, speaker->soundParms[6]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, speaker->soundParms[7]);
}

/*
 ==================
 SG_WriteBaseline_Player
 ==================
*/
static void SG_WriteBaseline_Player (msg_t *msg, entityState_t *baseline){

	player_t	*player = (player_t *)baseline->buffer;

	glqMsgSystem->WriteDeltaLong(msg, 0, player->bounds);

	glqMsgSystem->WriteDeltaBits(msg, 0, player->model, ES_MODEL_BITS);

	glqMsgSystem->WriteDeltaBits(msg, 0, player->sound, ES_SOUND_SHADER_BITS);
	glqMsgSystem->WriteDeltaLong(msg, 0, player->soundTime);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, player->soundParms[0]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, player->soundParms[1]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, player->soundParms[2]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, player->soundParms[3]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, player->soundParms[4]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, player->soundParms[5]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, player->soundParms[6]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, player->soundParms[7]);
}

/*
 ==================

 ==================
*/
static void SG_WriteBaseline_Item (msg_t *msg, entityState_t *baseline){

	item_t	*item = (item_t *)baseline->buffer;

	glqMsgSystem->WriteDeltaLong(msg, 0, item->bounds);

	glqMsgSystem->WriteDeltaBits(msg, 0, item->model, ES_MODEL_BITS);

	glqMsgSystem->WriteDeltaBits(msg, 0, item->sound, ES_SOUND_SHADER_BITS);
	glqMsgSystem->WriteDeltaLong(msg, 0, item->soundTime);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, item->soundParms[0]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, item->soundParms[1]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, item->soundParms[2]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, item->soundParms[3]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, item->soundParms[4]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, item->soundParms[5]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, item->soundParms[6]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, item->soundParms[7]);
}

/*
 ==================

 ==================
*/
static void SG_WriteBaseline_Monster (msg_t *msg, entityState_t *baseline){

	monster_t	*monster = (monster_t *)baseline->buffer;

	glqMsgSystem->WriteDeltaLong(msg, 0, monster->bounds);

	glqMsgSystem->WriteDeltaBits(msg, 0, monster->model, ES_MODEL_BITS);

	glqMsgSystem->WriteDeltaBits(msg, 0, monster->sound, ES_SOUND_SHADER_BITS);
	glqMsgSystem->WriteDeltaLong(msg, 0, monster->soundTime);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, monster->soundParms[0]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, monster->soundParms[1]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, monster->soundParms[2]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, monster->soundParms[3]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, monster->soundParms[4]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, monster->soundParms[5]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, monster->soundParms[6]);
	glqMsgSystem->WriteDeltaFloat(msg, 0.0f, monster->soundParms[7]);
}

/*
 ==================

 ==================
*/
static void SG_WriteBaseline_Projectile (msg_t *msg, entityState_t *baseline){

	projectile_t	*projectile = (projectile_t *)baseline->buffer;
}

/*
 ==================

 ==================
*/
static void SG_WriteBaseline (msg_t *msg, entityState_t *baseline){
	
	int		i;

	glqMsgSystem->WriteBits(msg, baseline->number, ES_NUMBER_BITS);
	glqMsgSystem->WriteBits(msg, baseline->type, ES_TYPE_BITS);
	glqMsgSystem->WriteBits(msg, baseline->flags, ES_FLAGS_BITS);

	// Write the position and orientation
	glqMsgSystem->WriteDeltaByte(msg, 0, baseline->origin.type);
	glqMsgSystem->WriteDeltaLong(msg, 0, baseline->origin.time);
	glqMsgSystem->WriteDeltaLong(msg, 0, baseline->origin.duration);
	glqMsgSystem->WriteDeltaVec3(msg, vec3_origin, baseline->origin.base);
	glqMsgSystem->WriteDeltaVec3(msg, vec3_origin, baseline->origin.delta);

	glqMsgSystem->WriteDeltaByte(msg, 0, baseline->angles.type);
	glqMsgSystem->WriteDeltaLong(msg, 0, baseline->angles.time);
	glqMsgSystem->WriteDeltaLong(msg, 0, baseline->angles.duration);
	glqMsgSystem->WriteDeltaAngles(msg, angles_zero, baseline->angles.base);
	glqMsgSystem->WriteDeltaAngles(msg, angles_zero, baseline->angles.delta);

	// Write the binding information
	glqMsgSystem->WriteDeltaBits(msg, 0, baseline->bindMaster, ES_BIND_MASTER_BITS);
	glqMsgSystem->WriteBool(msg, baseline->bindOrientated);

	// Write the event queue
	glqMsgSystem->WriteDeltaLong(msg, 0, baseline->eventSequence);

	for (i = 0; i < MAX_EVENTS; i++){
		glqMsgSystem->WriteDeltaByte(msg, 0, baseline->eventTypes[i]);
		glqMsgSystem->WriteDeltaWord(msg, 0, baseline->eventParms[i]);
	}

	// Write the state buffer
	switch (baseline->type){
	case ET_GENERAL:
		SG_WriteBaseline_General(msg, baseline);
		break;
	case ET_MOVER:
		SG_WriteBaseline_Mover(msg, baseline);
		break;
	case ET_BEAM:
		SG_WriteBaseline_Beam(msg, baseline);
		break;
	case ET_LIGHT:
		SG_WriteBaseline_Light(msg, baseline);
		break;
	case ET_SPEAKER:
		SG_WriteBaseline_Speaker(msg, baseline);
		break;
	case ET_PLAYER:
		SG_WriteBaseline_Player(msg, baseline);
		break;
	case ET_ITEM:
		SG_WriteBaseline_Item(msg, baseline);
		break;
	case ET_MONSTER:
		SG_WriteBaseline_Monster(msg, baseline);
		break;
	case ET_PROJECTILE:
		SG_WriteBaseline_Projectile(msg, baseline);
		break;
	default:
		G_Error("SG_WriteBaseline: bad entity type (%i)", baseline->type);
	}
}


/*
 ==============================================================================

 GAME STATE

 ==============================================================================
*/


/*
 ==================
 SG_WriteGameState
 ==================
*/
void SG_WriteGameState (msg_t *msg, int offset){

	int		startBits, endBits;
	int		index, count = 0;

	// Write config strings if needed
	if (offset >= GS_CONFIGSTRINGS_FIRST && offset <= GS_CONFIGSTRINGS_LAST){
		// Leave space for header
		startBits = glqMsgSystem->GetWriteState(msg);

		glqMsgSystem->WriteLong(msg, 0);
		glqMsgSystem->WriteLong(msg, 0);
		glqMsgSystem->WriteLong(msg, 0);

		// Write as many config strings as possible
		index = offset - GS_CONFIGSTRINGS_FIRST;

		while (index < MAX_CONFIG_STRINGS){
			if (!level.configStrings[index]){
				index++;
				continue;
			}

			if (glqMsgSystem->RemainingWriteBytes(msg) < Str_Length(level.configStrings[index]) + 32)
				break;

			glqMsgSystem->WriteShort(msg, index);
			glqMsgSystem->WriteString(msg, level.configStrings[index]);

			count++;
			index++;
		}

		if (index != MAX_CONFIG_STRINGS)
			index = GS_CONFIGSTRINGS_FIRST + index;
		else
			index = GS_BASELINES_FIRST;

		// Write the header
		endBits = glqMsgSystem->GetWriteState(msg);

		glqMsgSystem->SetWriteState(msg, startBits);

		glqMsgSystem->WriteLong(msg, offset);	// Start offset
		glqMsgSystem->WriteLong(msg, count);	// Number of config strings
		glqMsgSystem->WriteLong(msg, index);	// Next offset

		glqMsgSystem->SetWriteState(msg, endBits);

		return;
	}

	// Write baselines if needed
	if (offset >= GS_BASELINES_FIRST && offset <= GS_BASELINES_LAST){
		// Leave space for header
		startBits = glqMsgSystem->GetWriteState(msg);

		glqMsgSystem->WriteLong(msg, 0);
		glqMsgSystem->WriteLong(msg, 0);
		glqMsgSystem->WriteLong(msg, 0);

		// Write as many baselines as possible
		index = offset - GS_BASELINES_FIRST;

		while (index < MAX_ENTITIES){
			if (level.baselines[index].type == ET_BAD){
				index++;
				continue;
			}

			if (glqMsgSystem->RemainingWriteBytes(msg) < sizeof(entityState_t) + 32)
				break;

			SG_WriteBaseline(msg, &level.baselines[index]);

			count++;
			index++;
		}

		if (index != MAX_ENTITIES)
			index = GS_BASELINES_FIRST + index;
		else
			index = -1;

		// Write the header
		endBits = glqMsgSystem->GetWriteState(msg);

		glqMsgSystem->SetWriteState(msg, startBits);

		glqMsgSystem->WriteLong(msg, offset);	// Start offset
		glqMsgSystem->WriteLong(msg, count);	// Number of baselines
		glqMsgSystem->WriteLong(msg, index);	// Next offset

		glqMsgSystem->SetWriteState(msg, endBits);

		return;
	}

	// Invalid game state request
	glqMsgSystem->WriteLong(msg, -1);
	glqMsgSystem->WriteLong(msg, -1);
	glqMsgSystem->WriteLong(msg, -1);
}